/******************************************************************************
* echo_client.c                                                               *
*                                                                             *
* Description: This file contains the C source code for an echo client.  The  *
*              client connects to an arbitrary <host,port> and sends input    *
*              from stdin.                                                    *
*                                                                             *
* Authors: Athula Balachandran <abalacha@cs.cmu.edu>,                         *
*          Wolf Richter <wolf@cs.cmu.edu>                                     *
*                                                                             *
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <fcntl.h> // 文件输入用

#define ECHO_PORT 9999
#define BUF_SIZE 4096//缓存区大小

int main(int argc, char* argv[])//argv应该为程序名 + 服务器 IP + 端口
{
    if (argc != 3&&argc != 4)
    {
        fprintf(stderr, "usage: %s <server-ip> <port>",argv[0]);
        return EXIT_FAILURE;
    }

    char buf[BUF_SIZE];//用于接收服务器返回的数据的缓冲区
        
    int status, sock;//status 存储函数返回状态 sock 是 socket 文件描述符；
    struct addrinfo hints;//hints 是查询服务器地址信息的“模板”
	memset(&hints, 0, sizeof(struct addrinfo));
    struct addrinfo *servinfo; //will point to the results 声明了一个指针变量
    hints.ai_family = AF_INET;  //IPv4
    hints.ai_socktype = SOCK_STREAM; //TCP stream sockets
    hints.ai_flags = AI_PASSIVE; //fill in my IP for me
    //使用 getaddrinfo 将主机名和端口号转换,将结果通过第四个参数（也就是 servinfo 的地址）返回一组地址结构链表的首地址。,servinfo。如果出错，打印错误信息并退出。
    if ((status = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0) 
    {
        fprintf(stderr, "getaddrinfo error: %s \n", gai_strerror(status));
        return EXIT_FAILURE;
    }
    //使用 servinfo 中的信息创建一个 TCP socket。如果失败，则退出。
    if((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1)
    {
        fprintf(stderr, "Socket failed");
        return EXIT_FAILURE;
    }
    //尝试连接服务器地址。如果失败，退出。
    if (connect (sock, servinfo->ai_addr, servinfo->ai_addrlen) == -1)
    {
        fprintf(stderr, "Connect");
        return EXIT_FAILURE;
    }
    // 如果给了第三个参数（文件），就从文件读取并发送一次
    if (argc == 4) {
        char request[BUF_SIZE];
        int fd = open(argv[3], O_RDONLY);
        if (fd < 0) {
            perror("Open file failed");
            close(sock);
            return EXIT_FAILURE;
        }

        ssize_t n = read(fd, request, BUF_SIZE-1);//从文件描述符 fd 中读取数据到request内存中。最多读取BUF_SIZE-1字节数
        if (n < 0) {
            perror("Read file failed");
            close(fd);
            close(sock);
            return EXIT_FAILURE;
        }
        request[n] = '\0'; // 加结束符
        close(fd);

        //发送文件里的信息
        printf("Sending message:\n");
        printf("%s\n",request);
        send(sock, request, strlen(request), 0);

        //接收信息
        char buffer[BUF_SIZE];
        int bytes_received;
        printf("\nReceiving response...\n");
        while ((bytes_received = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
            buffer[bytes_received] = '\0';
            printf("%s", buffer);
        }
        if (bytes_received == 0) {
            printf("\nConnection closed by server\n");
        } else if (bytes_received < 0) {
            perror("recv failed");
        }
    } else {
        // 否则进入循环从 stdin 交互式输入
        while(1){
            printf("\nInput message: ");
            //从标准输入读取一行文字，存储到 msg 中。    
            char msg[BUF_SIZE]; 
            fgets(msg, BUF_SIZE, stdin);
            //打印即将发送的内容；使用 send 将消息发送到服务器。
            int bytes_received;
            fprintf(stdout, "Sending %s", msg);
            send(sock, msg , strlen(msg), 0);
            if((bytes_received = recv(sock, buf, BUF_SIZE, 0)) > 1)//调用 recv 接收服务器返回的数据；
            {
                buf[bytes_received] = '\0';//加上 '\0' 作为字符串结束符；
                fprintf(stdout, "Received %s", buf);//输出返回的信息。
                memset(buf, 0, BUF_SIZE);//清空缓存区
            }        
        }
    }
    
    freeaddrinfo(servinfo);
    close(sock);    
    return EXIT_SUCCESS;
}
